## from Twisted Python
# see LICENSE.txt for license information

from traceback import print_exc

class AlreadyCalledError(Exception):
    pass

def passthru(arg):
    return arg

class Failure:
    def __init__(self, fail):
        self.fail = fail

    def __str__(self):
        return str(self.fail)
    
class Deferred:
    called = 0
    paused = 0
    def __init__(self):
        self.callbacks = []

    def addCallbacks(self, callback, errback=None,
                     callbackArgs=None, callbackKeywords=None,
                     errbackArgs=None, errbackKeywords=None):
        assert hasattr(callback, '__call__')
        assert errback == None or hasattr(errback, '__call__')
        cbs = ((callback, callbackArgs, callbackKeywords),
               (errback or (passthru), errbackArgs, errbackKeywords))
        self.callbacks.append(cbs)
            
        if self.called:
            self._runCallbacks()
        return self

    def addCallback(self, callback, *args, **kw):
        return self.addCallbacks(callback, callbackArgs=args,
                                 callbackKeywords=kw)

    def addErrback(self, errback, *args, **kw):
        return self.addCallbacks(passthru, errback,
                                 errbackArgs=args,
                                 errbackKeywords=kw)

    def addBoth(self, callback, *args, **kw):
        return self.addCallbacks(callback, callback,
                                 callbackArgs=args, errbackArgs=args,
                                 callbackKeywords=kw, errbackKeywords=kw)

    def chainDeferred(self, d):
        return self.addCallbacks(d.callback, d.errback)

    def callback(self, result):
        assert not isinstance(result, Deferred)
        self._startRunCallbacks(result)


    def errback(self, fail=None):
        if not isinstance(fail, Failure):
            fail = Failure(fail)

        self._startRunCallbacks(fail)


    def pause(self):
        self.paused = self.paused + 1


    def unpause(self):
        self.paused = self.paused - 1
        if self.paused:
            return
        if self.called:
            self._runCallbacks()

    def _continue(self, result):
        self.result = result
        self.unpause()

    def _startRunCallbacks(self, result):
        if self.called:
            raise AlreadyCalledError
        self.called = True
        self.result = result
        self._runCallbacks()

    def _runCallbacks(self):
        if not self.paused:
            cb = self.callbacks
            self.callbacks = []
            while cb:
                item = cb.pop(0)
                callback, args, kw = item[
                    isinstance(self.result, Failure)]
                args = args or ()
                kw = kw or {}
                try:
                    self.result = callback(self.result, *args, **kw)
                    if isinstance(self.result, Deferred):
                        self.callbacks = cb
                        self.pause()
                        self.result.addBoth(self._continue)
                        break
                except:
                    pass
##                    print "DHT DEBUG, NOT(!) ERROR:"
##                    print_exc()
